package com.zzw.bridge.business.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.zzw.bridge.business.dao.ZzwPierColumnDao;
import com.zzw.bridge.business.dao.ZzwPierColumnReportDao;
import com.zzw.bridge.business.dao.ZzwPierColumnReportDetailDao;
import com.zzw.bridge.business.dao.ZzwPierColumnReportDetailHisDao;
import com.zzw.bridge.business.dao.ZzwPierColumnReportHisDao;
import com.zzw.bridge.business.dao.ZzwPierProjectDao;
import com.zzw.bridge.business.dto.ZzwPierColumnDTO;
import com.zzw.bridge.business.dto.ZzwPierColumnReportDTO;
import com.zzw.bridge.business.dto.ZzwPierColumnReportDetailDTO;
import com.zzw.bridge.business.service.ReceiveDataService;
import com.zzw.bridge.business.service.ZzwFileReportAssociationService;
import com.zzw.bridge.business.vo.ZzwPierProjectVo;
import com.zzw.bridge.common.entitys.business.ZzwFileReportAssociationEntity;
import com.zzw.bridge.common.exception.ErrorCode;
import com.zzw.bridge.common.exception.ServerException;
import com.zzw.bridge.common.utils.GsonUtil;
import com.zzw.bridge.common.utils.Result;
import com.zzw.bridge.security.cache.TokenStoreCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 接收处理外部数据Service
 *
 * @author XiaoJie
 * @date 2025/5/16 11:34
 */
@Service
@Slf4j
public class ReceiveDataServiceImpl implements ReceiveDataService {

    @Value("${file.upload-dir:D:/UserHome/testzip}")
    private String uploadDir;

    @Value("${file.temp-dir:D:/UserHome/testzip}")
    private String tempDir;

    @Value("${file.buffer-size}")
    private int bufferSize; // 缓冲区大小，单位字节

    // 异步处理线程池
    // 使用 Executors 工具类创建一个固定大小的线程池
    // 线程池的大小由当前运行环境的可用处理器核心数乘以 2 决定
    // Runtime.getRuntime().availableProcessors() 方法用于获取当前系统的可用处理器核心数
    // 乘以 2 是为了充分利用 CPU 资源，在处理 I/O 密集型任务时，能让线程在等待 I/O 操作时，其他线程可以继续执行任务，提高整体的处理效率
    private final ExecutorService executorService = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors() * 2);

    @Resource
    private ZzwPierColumnReportDao zzwPierColumnReportDao;

    @Resource
    private ZzwPierColumnReportHisDao zzwPierColumnReportHisDao;

    @Resource
    private ZzwPierColumnReportDetailDao zzwPierColumnReportDetailDao;

    @Resource
    private ZzwPierColumnReportDetailHisDao zzwPierColumnReportDetailHisDao;

    @Resource
    private ZzwFileReportAssociationService zzwFileReportAssociationService;

    @Resource
    private TokenStoreCache tokenStoreCache;

    @Resource
    private ZzwPierProjectDao zzwPierProjectDao;

    @Resource
    private ZzwPierColumnDao zzwPierColumnDao;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean receiveData(String data) {
        ZzwPierColumnReportDTO zzwPierColumnReportDto;
        List<ZzwPierColumnReportDetailDTO> list = null;
        try {
            Gson gson = GsonUtil.getGson();
            zzwPierColumnReportDto = gson.fromJson(data, ZzwPierColumnReportDTO.class);
            JsonObject jsonObject = gson.fromJson(data, JsonObject.class);
            JsonElement element = jsonObject.get("sliceList");
            if (ObjectUtil.isNotNull(element)) {
                JsonArray sliceList = element.getAsJsonArray();
                list = GsonUtil.gsonToList(sliceList.toString(), ZzwPierColumnReportDetailDTO.class);
            }
            //zzwPierColumnReportDto = JsonUtils.parseObject(data, ZzwPierColumnReportDTO.class);
            log.info("转换后的墩柱报告数据：{}",zzwPierColumnReportDto);
            log.info("转换后的墩柱切片数据：{}",list);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new ServerException(ErrorCode.JSON_CONVERT_ERROR);
        }
        zzwPierColumnReportDao.insertPierColumnReport(zzwPierColumnReportDto);
        //备份表
        zzwPierColumnReportHisDao.insertPierColumnReport(zzwPierColumnReportDto);
        if (CollUtil.isNotEmpty(list)) {
            zzwPierColumnReportDetailDao.insertBatchPierColumnReportDetail(list);
            //备份表
            zzwPierColumnReportDetailHisDao.insertBatchPierColumnReportDetail(list);
        }
        ZzwPierColumnDTO zzwPierColumnDTO = ZzwPierColumnDTO.builder()
                .columnName(zzwPierColumnReportDto.getName())
                .reportId(zzwPierColumnReportDto.getReportId())
                .columnType(zzwPierColumnReportDto.getType())
                .projectCode(zzwPierColumnReportDto.getProjectCode())
                .build();
        zzwPierColumnDao.insertColumn(zzwPierColumnDTO);
        return true;
    }

    @Override
    public List<ZzwPierProjectVo> getProjectList(HttpServletRequest request) {
        return zzwPierProjectDao.selectListByUsername(tokenStoreCache.getCurrentUser(request).getUsername());
    }



    @Async("fileTaskExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void storeAndExtractZipFile(MultipartFile file, String reportId) throws IOException {
        // 根据临时目录和上传文件唯一标识构建临时文件路径
        Path tempFilePath = Paths.get(tempDir, file.getOriginalFilename());
        try (
            // 获取上传文件的输入流
            InputStream inputStream = file.getInputStream();
            // 创建文件输出流，用于将上传文件写入临时文件
            FileOutputStream fos = new FileOutputStream(tempFilePath.toFile());
            // 获取文件输出流对应的文件通道，用于高效的文件传输
            FileChannel outputChannel = fos.getChannel()
        ) {
            // 使用NIO的transferTo方法提高性能
            // 使用 NIO 的 Channels 工具类将输入流转换为可读字节通道
            ReadableByteChannel inputChannel = Channels.newChannel(inputStream);
            // 初始化文件传输的起始位置
            long position = 0;
            // 记录每次传输的字节数
            long count;
            // 获取上传文件的大小
            long fileSize = file.getSize();

            // 每次传输 8MB（8 * 1024 * 1024 = 8388608 字节），平衡内存使用和传输效率
            while (position < fileSize) {
                // 使用文件通道的 transferFrom 方法从输入通道向输出通道传输数据
                //每次传输的最大字节数，即 8MB（因为 1MB = 1024 * 1024 字节，8 * 1024 * 1024 = 8388608）
                count = outputChannel.transferFrom(inputChannel, position, 8388608);
                // 更新传输位置
                position += count;
            }
        } catch (Exception e) {
            // 上传过程中出现异常，删除临时文件
            Files.deleteIfExists(tempFilePath);
            // 重新抛出异常
            e.printStackTrace();
            throw new ServerException(ErrorCode.FILE_HANDLE_ERROR);
        }

        // 提交一个异步任务到线程池，用于解压临时文件
//        executorService.submit(() -> {
//            try {
//                // 调用 extractZipFile 方法将临时文件解压到指定的上传目录
//                List<String> strings = extractZipFile(tempFilePath.toFile(), Paths.get(uploadDir).toFile(),reportId);
//                // 解压完成后，删除临时文件
//                Files.deleteIfExists(tempFilePath); // 解压后删除临时文件
//                log.info("生成的文件名集合------>：{}",strings);
//            } catch (Exception e) {
//                try {
//                    Files.deleteIfExists(tempFilePath);
//                } catch (IOException ex) {
//                    throw new ServerException(ErrorCode.FILE_HANDLE_ERROR);
//                }
//                // 记录解压过程中出现的异常信息
//                e.printStackTrace();
//            }
//        });

        // 提交一个异步任务到线程池，用于解压临时文件
         CompletableFuture.supplyAsync(() -> {
            try {
                // 调用 extractZipFile 方法将临时文件解压到指定的上传目录
                List<Path> paths = extractZipFile(tempFilePath.toFile(), Paths.get(uploadDir).toFile());
                // 解压完成后，删除临时文件
                Files.deleteIfExists(tempFilePath); // 解压后删除临时文件
                log.info("生成的文件名集合------>：{}", paths);
                return paths;
            } catch (Exception e) {
                // 记录解压过程中出现的异常信息
                e.printStackTrace();
                throw new ServerException(ErrorCode.FILE_HANDLE_ERROR);
            }finally {
                try {
                    Files.deleteIfExists(tempFilePath);
                } catch (IOException ex) {
                    log.error(ex.getMessage());
                }
            }
        }, executorService).thenApply(result-> saveBathFileAssociation(reportId, result));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<String> receiveProjectData(String data) {
        List<ZzwPierProjectVo> list = GsonUtil.jsonToArrayList(data,ZzwPierProjectVo.class);
        if (ObjectUtil.isNull(list)) {
            return Result.error("数据不可为空！！！");
        }
        log.info("转换的数据：{}",list);
        //List<ZzwPierProjectVo> subsetList = getAllSubsetList(list);
        //log.info("所有子集：{}",list);
        List<String> ids = list.stream().map(ZzwPierProjectVo::getId).collect(Collectors.toList());
        List<String> idList = zzwPierProjectDao.selectListIds(ids);
        ids.removeAll(idList);
        List<ZzwPierProjectVo> collect = list.stream().filter(project -> ids.contains(project.getId())).collect(Collectors.toList());
        String format="无可同步数据";
        if (ObjectUtil.isNotNull(collect)&&!collect.isEmpty()) {
             format = String.format("成功新增%s条项目", zzwPierProjectDao.insertBachProject(collect));
        }
        return Result.ok(format);
    }

    /**
     * 递归提取所有子项目
     * @param list 项目列表
     *
     * @return 包含所有子项目的列表
     */
    private  List<ZzwPierProjectVo> getAllSubsetList(List<ZzwPierProjectVo> list) {
        List<ZzwPierProjectVo> allSubsets = new ArrayList<>();
        if (ObjectUtil.isNull(list)) {
            return allSubsets;
        }
        list.forEach(project->{
            if (ObjectUtil.isNotNull(project)) {
                allSubsets.add(project);
                if (ObjectUtil.isNotNull(project.getSubsetList())) {
                    // 递归调用获取子项目列表
                    allSubsets.addAll(getAllSubsetList(project.getSubsetList()));
                }
            }
        });
        return allSubsets;
    }

    /**
     * 保存文件关联信息
     *
     * @param reportId "报表ID"
     * @param result "文件名集合"
     * @return {@link boolean}
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBathFileAssociation(String reportId, List<Path> result) {
        //处理结果
        if (CollUtil.isNotEmpty(result)) {
            List<ZzwFileReportAssociationEntity> list = result.stream().map(path -> ZzwFileReportAssociationEntity
                    .builder().id(UUID.randomUUID().toString().replace("-", ""))
                    .fileName(String.valueOf(path.getFileName()))
                    .reportId(reportId)
                    //将windows路径替换为linux路劲
                    .fileUrl(path.getParent()==null?"":String.valueOf(path.getParent()).replace("\\","/"))
                    .cloudId(String.valueOf(path.getName(path.getNameCount()-2)))
                    .createTime(new Date())
                    .updateTime(new Date())
                    .isDelete(0)
                    .build()).collect(Collectors.toList());
            return zzwFileReportAssociationService.saveBatchByList(list);
        }
        return false;
    }

    /**
     * 解压指定的 ZIP 文件到目标目录-只保留文件
     *
     * @param zipFile 要解压的 ZIP 文件
     * @param destDirectory 解压目标目录
     * @throws IOException 当文件操作发生 I/O 异常时抛出
     */
    private List<String> extractZipFile2(File zipFile, File destDirectory, String reportId) throws IOException {
        //生成的文件名集合
        ArrayList<String> fileNameList = new ArrayList<>();
        // 创建解压目录，如果目标目录不存在，则递归创建该目录
        if (!destDirectory.exists()) {
            destDirectory.mkdirs();
        }

        // 使用 NIO 方式解压文件，创建 ZipInputStream 并设置较大的缓冲区，同时指定字符编码为 GBK
        try (ZipInputStream zipInputStream = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(zipFile), bufferSize), Charset.forName("GBK"))) {

            // 获取 ZIP 文件中的下一个条目
            ZipEntry zipEntry = zipInputStream.getNextEntry();

            // 遍历 ZIP 文件中的所有条目
            while (zipEntry != null) {
                // 如果当前条目是目录
                if (zipEntry.isDirectory()) {
                    // 关闭当前 ZIP 条目
                    zipInputStream.closeEntry();
                    // 获取下一个 ZIP 条目
                    zipEntry = zipInputStream.getNextEntry();
                    // 跳过本次循环，继续处理下一个条目
                    continue;
                }

                // 生成唯一的文件名，使用 UUID 确保文件名的唯一性
                String uniqueFileName = UUID.randomUUID().toString().replace("-","");
                uniqueFileName = String.format("%s-%s",uniqueFileName,reportId);
                // 获取当前 ZIP 条目文件名的扩展名
                String fileExtension = getFileExtension(zipEntry.getName());
                // 如果扩展名不为空，则将扩展名添加到唯一文件名后
                if (!fileExtension.isEmpty()) {
                    uniqueFileName += "." + fileExtension;
                }

                // 创建目标文件对象，将唯一文件名与目标目录组合
                File destFile = new File(destDirectory, uniqueFileName);

                // 确保目标文件的父目录存在，如果不存在则递归创建
                File parentDir = destFile.getParentFile();
                if (!parentDir.exists()) {
                    parentDir.mkdirs();
                }

                // 使用 NIO 方式写入文件，创建 BufferedOutputStream 并设置较大的缓冲区
                try (BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(destFile), bufferSize)) {

                    // 创建字节缓冲区，用于存储从 ZIP 输入流读取的数据
                    byte[] buffer = new byte[bufferSize];
                    int bytesRead;
                    // 从 ZIP 输入流读取数据到缓冲区，直到读取完所有数据
                    while ((bytesRead = zipInputStream.read(buffer)) != -1) {
                        // 将缓冲区中的数据写入目标文件
                        bos.write(buffer, 0, bytesRead);
                    }
                    fileNameList.add(uniqueFileName);
                }

                // 关闭当前 ZIP 条目
                zipInputStream.closeEntry();
                // 获取下一个 ZIP 条目
                zipEntry = zipInputStream.getNextEntry();
            }
        }
        return fileNameList;
    }

    /**
     * 解压ZIP文件，保留原始目录结构和文件名，返回解压后的文件路径集合
     *
     * @param zipFile 要解压的 ZIP 文件
     * @param destDirectory 解压目标目录
     * @throws IOException 当文件操作发生 I/O 异常时抛出
     */
    private List<Path> extractZipFile(File zipFile, File destDirectory) throws IOException {
        // 确保目标目录存在
        if (!destDirectory.exists()) {
            destDirectory.mkdirs();
        }

        // 用于存储解压后的文件路径
        List<Path> filePaths = new ArrayList<>();

        // 根据ZIP文件名创建子目录（去除扩展名）
        String zipFileName = zipFile.getName();
        String subDirName = zipFileName.substring(0, zipFileName.lastIndexOf('.'));
        File subDirectory = new File(destDirectory, subDirName);

        // 确保子目录存在
        if (!subDirectory.exists()) {
            if (!subDirectory.mkdirs()) {
                throw new IOException("无法创建子目录: " + subDirectory.getAbsolutePath());
            }
        }

        //  使用缓冲流和ZIP输入流，提高解压效率
        try (ZipInputStream zipInputStream = new ZipInputStream(
                new BufferedInputStream(Files.newInputStream(zipFile.toPath()), bufferSize),Charset.forName("GBK"))) {

            ZipEntry zipEntry = zipInputStream.getNextEntry();

            while (zipEntry != null) {
                //  构建完整的目标文件路径（保留原始目录结构和文件名）
                Path entryPath = Paths.get(subDirectory.getAbsolutePath(), zipEntry.getName());

                //  防御路径遍历攻击（确保解压路径在目标目录内）
                if (!entryPath.normalize().startsWith(subDirectory.toPath())) {
                    throw new SecurityException("非法ZIP条目：" + zipEntry.getName());
                }

                //  处理目录条目
                if (zipEntry.isDirectory()) {
                    // 创建目录（包括所有父目录）
                    Files.createDirectories(entryPath);
                    zipInputStream.closeEntry();
                    zipEntry = zipInputStream.getNextEntry();
                    continue;
                }

                // 确保父目录存在
                Path parentPath = entryPath.getParent();
                if (parentPath != null && !Files.exists(parentPath)) {
                    Files.createDirectories(parentPath);
                }

                // 使用缓冲输出流写入文件内容
                try (BufferedOutputStream bos = new BufferedOutputStream(
                        Files.newOutputStream(entryPath.toFile().toPath()), bufferSize)) {

                    byte[] buffer = new byte[bufferSize];
                    int bytesRead;

                    //  分块读取并写入，避免大内存占用
                    while ((bytesRead = zipInputStream.read(buffer)) != -1) {
                        bos.write(buffer, 0, bytesRead);
                    }

                    //将解压后的文件路径添加到结果列表
                    filePaths.add(entryPath);

                } catch (IOException e) {
                    //  单个文件解压失败，记录错误并继续处理其他文件
                    log.error("Failed to extract file: " + zipEntry.getName() + " - " + e.getMessage());
                }

                zipInputStream.closeEntry();
                zipEntry = zipInputStream.getNextEntry();
            }
        }

        // 12. 返回解压后的文件路径集合
        return filePaths;
    }

    private String getFileExtension(String fileName) {
        // 使用 lastIndexOf 方法查找文件名中最后一个点（.）的索引位置
        // 最后一个点之后的部分通常是文件扩展名
        int lastIndexOf = fileName.lastIndexOf(".");
        // 没有扩展名时，返回空字符串
        if (lastIndexOf == -1) {
            return "";
        }
        // 当文件名中存在点（.）时，使用 substring 方法截取最后一个点之后的部分作为文件扩展名
        return fileName.substring(lastIndexOf + 1);
    }

}
